package com.shop.www.ssm.ioc;

import com.shop.www.ssm.util.StringUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String,Object> beanMap = new HashMap<>();
    /*private String path = "applicationContext.xml" ;*/
      /* public ClassPathXmlApplicationContext(){this("applicationContext.xml");}*/
    public ClassPathXmlApplicationContext(String path){
        if(StringUtil.isEmpty(path)){
            throw new RuntimeException("IOC容器的配置文件没有指定...");
        }
        try {
            //getClassLoader().getResourceAsStream(path)默认则是从ClassPath根下获取，最终是由ClassLoader获取资源。

            //把要解析的 XML 文档转化为输入流，以便 DOM 解析器解析它    path=applicationContext.xml
            //getClass()：取得当前对象所属的Class对象
            //getClass()也就是通过反射获取类的具体
            //getClassLoader()：取得该Class对象的类装载器
            //类装载器负责从Java字符文件将字符流读入内存，并构造Class类对象，在你说的问题哪里，通过它可以得到一个文件的输入流
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);

            //1.创建DocumentBuilderFactory
            //调用 DocumentBuilderFactory.newInstance() 方法得到创建 DOM 解析器的工厂
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            //2.创建DocumentBuilder对象
            //调用工厂对象的 newDocumentBuilder方法得到 DOM 解析器对象
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
            //3.创建Document对象
            //调用 DOM 解析器对象的 parse() 方法解析 XML 文档，得到代表整个文档的 Document 对象，进行可以利用DOM特性对整个XML文档进行操作了
            Document document = documentBuilder.parse(inputStream);

            //4.获取所有的bean节点
            //getElementsByTagName将返回一个 NodeList 对象（可以作为只读数组处理）
            //该对象存放文档中具有指定标签名的所有 Element 节点，它们存放的顺序就是在源文档中出现的顺序。
            NodeList beanNodeList = document.getElementsByTagName("bean");
            for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                //item(int index)   返回集合中的第 index 个项（Node类型）
                Node beanNode = beanNodeList.item(i);
                //判断beanNode是否是元素节点
                if(beanNode.getNodeType() == Node.ELEMENT_NODE){
                    //强转为Element，因为有好用的getAtttribute方法
                    Element beanElement = (Element)beanNode ;
                    String beanId =  beanElement.getAttribute("id");
                    String className = beanElement.getAttribute("class");
                    Class beanClass = Class.forName(className);
                    //创建bean实例
                    Object beanObj = beanClass.newInstance() ;
                    //将bean实例对象保存到map容器中
                    beanMap.put(beanId , beanObj) ;
                    //到目前为止，此处需要注意的是，bean和bean之间的依赖关系还没有设置
                }
            }
            //5.组装bean之间的依赖关系
            for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                Node beanNode = beanNodeList.item(i);
                if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element beanElement = (Element) beanNode;
                    String beanId = beanElement.getAttribute("id");
                    NodeList beanChildNodeList = beanElement.getChildNodes();
                    for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
                        Node beanChildNode = beanChildNodeList.item(j);
                        if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
                            Element propertyElement = (Element) beanChildNode;
                            String propertyName = propertyElement.getAttribute("name");
                            String propertyRef = propertyElement.getAttribute("ref");
                            //1) 找到propertyRef对应的实例
                            Object refObj = beanMap.get(propertyRef);
                            //2) 将refObj设置到当前bean对应的实例的property属性上去
                            Object beanObj = beanMap.get(beanId);
                            //beanClazz为父节点
                            Class beanClazz = beanObj.getClass();
                            //propertyName是父节点中定义的子节点的成员
                            Field propertyField = beanClazz.getDeclaredField(propertyName);
                            //禁用安全检查，以反射的方式去写入数据
                            propertyField.setAccessible(true);
                            //给beanObj也就是父类的propertyField(需要调用的类)赋上refObj(类的实际名)
                            //把beanObj中定义的子节点也就是propertyField赋上refObj
                            propertyField.set(beanObj,refObj);
                        }
                    }
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}
